]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
Don't destroy and recreate workspaces on view switches
authorFlorian Müllner <fmuellner@src.gnome.org>
Sun, 14 Feb 2010 23:32:57 +0000 (00:32 +0100)
committerFlorian Müllner <fmuellner@src.gnome.org>
Thu, 18 Mar 2010 22:47:55 +0000 (23:47 +0100)
Currently, the workspace objects are destroyed and recreated on
view switches. Instead, keep the objects around and reparent the
workspace actors on view switches.

https://bugzilla.gnome.org/show_bug.cgi?id=610191

js/ui/overview.js
js/ui/workspace.js
js/ui/workspacesView.js

index 070d6766e8899c1c4778f3e8a49279509590882e..8bf7d93cf09c8a105acad75dc6febc997acd19f2 100644 (file)
@@ -201,8 +201,7 @@ Overview.prototype = {
         this.infoBar = new InfoBar();
         this._group.add_actor(this.infoBar.actor);
 
-        this._workspacesControls = new WorkspacesView.WorkspacesControls();
-        this._workspacesControls.connect('view-changed', Lang.bind(this, this._onViewChanged));
+        this._workspacesManager = null;
 
         this.visible = false;
         this.animationInProgress = false;
@@ -258,13 +257,8 @@ Overview.prototype = {
     _onViewChanged: function() {
         if (!this.visible)
             return;
-        // Remove old workspacesView
-        this._workspaces.hide();
-        this._group.remove_actor(this._workspaces.actor);
-        this._workspaces.destroy();
 
-        this._workspaces = this._workspacesControls.createCurrentWorkspaceView(this._workspacesWidth, this._workspacesHeight,
-                                                                               this._workspacesX, this._workspacesY, false);
+        this._workspaces = this._workspacesManager.workspacesView;
 
         // Show new workspacesView
         this._group.add_actor(this._workspaces.actor);
@@ -459,8 +453,14 @@ Overview.prototype = {
         this._dash.show();
 
         /* TODO: make this stuff dynamic */
-        this._workspaces = this._workspacesControls.createCurrentWorkspaceView(this._workspacesWidth, this._workspacesHeight,
-                                                                               this._workspacesX, this._workspacesY, true);
+        this._workspacesManager =
+            new WorkspacesView.WorkspacesManager(this._workspacesWidth,
+                                                 this._workspacesHeight,
+                                                 this._workspacesX,
+                                                 this._workspacesY);
+        this._workspacesManager.connect('view-changed',
+                                        Lang.bind(this, this._onViewChanged));
+        this._workspaces = this._workspacesManager.workspacesView;
         this._group.add_actor(this._workspaces.actor);
 
         // The workspaces actor is as big as the screen, so we have to raise the dash above it
@@ -468,7 +468,7 @@ Overview.prototype = {
         // be as big as the screen.
         this._dash.actor.raise(this._workspaces.actor);
 
-        this._workspacesBar = this._workspacesControls.actor;
+        this._workspacesBar = this._workspacesManager.controlsBar.actor;
         this._workspacesBar.set_position(this._workspacesBarX,
                                          this._workspacesBarY);
         this._workspacesBar.width = this._workspacesBarWidth;
@@ -593,6 +593,8 @@ Overview.prototype = {
         this._workspacesBar.destroy();
         this._workspacesBar = null;
 
+        this._workspacesManager = null;
+
         this._dash.hide();
         this._group.hide();
 
index e010e8dd16ef155cc92bf6f7c63e627ad5d134d4..4c58a49e1807b211627b3ada308438f8b29ddb1f 100644 (file)
@@ -639,34 +639,30 @@ const WindowPositionFlags = {
 };
 
 /**
- * @workspaceNum: Workspace index
- * @parentActor: The actor which will be the parent of this workspace;
- *               we need this in order to add chrome such as the icons
- *               on top of the windows without having them be scaled.
+ * @metaWorkspace: a #Meta.Workspace
  */
-function Workspace(workspaceNum, parentActor) {
-    this._init(workspaceNum, parentActor);
+function Workspace(metaWorkspace) {
+    this._init(metaWorkspace);
 }
 
 Workspace.prototype = {
-    _init : function(workspaceNum, parentActor) {
+    _init : function(metaWorkspace) {
         // When dragging a window, we use this slot for reserve space.
         this._reservedSlot = null;
-        this.workspaceNum = workspaceNum;
+        this.metaWorkspace = metaWorkspace;
         this._windowOverlaysGroup = new Clutter.Group();
         // Without this the drop area will be overlapped.
         this._windowOverlaysGroup.set_size(0, 0);
 
-        this._metaWorkspace = global.screen.get_workspace_by_index(workspaceNum);
-
-        parentActor.add_actor(this._windowOverlaysGroup);
-        this._parentActor = parentActor;
-
         this.actor = new Clutter.Group();
         this.actor._delegate = this;
 
         this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
 
+        // Items in _windowOverlaysGroup should not be scaled, so we don't
+        // add them to this.actor, but to its parent whenever it changes
+        this.actor.connect('parent-set', Lang.bind(this, this._onParentSet));
+
         // Auto-sizing is unreliable in the presence of ClutterClone, so rather than
         // implicitly counting on the workspace actor to be sized to the size of the
         // included desktop actor clone, set the size explicitly to the screen size.
@@ -695,7 +691,7 @@ Workspace.prototype = {
                                             // We check for "2" here because the Desktop does not really count
                                             // as a window in this context.
                                             if (this._windows.length <= 2) {
-                                                this._metaWorkspace.activate(time);
+                                                this.metaWorkspace.activate(time);
                                                 Main.overview.hide();
                                             }
                                         }));
@@ -715,9 +711,9 @@ Workspace.prototype = {
         this._showOnlyWindows = null;
 
         // Track window changes
-        this._windowAddedId = this._metaWorkspace.connect('window-added',
+        this._windowAddedId = this.metaWorkspace.connect('window-added',
                                                           Lang.bind(this, this._windowAdded));
-        this._windowRemovedId = this._metaWorkspace.connect('window-removed',
+        this._windowRemovedId = this.metaWorkspace.connect('window-removed',
                                                             Lang.bind(this, this._windowRemoved));
         this._repositionWindowsId = 0;
 
@@ -737,6 +733,17 @@ Workspace.prototype = {
         return -1;
     },
 
+    _onParentSet: function(actor, old_parent) {
+        let new_parent = this.actor.get_parent();
+        if (new_parent == null)
+            return;
+
+        if (old_parent)
+            this._windowOverlaysGroup.reparent(new_parent);
+        else
+            new_parent.add_actor(this._windowOverlaysGroup);
+    },
+
     /**
      * lookupCloneForMetaWindow:
      * @metaWindow: A #MetaWindow
@@ -819,6 +826,16 @@ Workspace.prototype = {
         }
     },
 
+    /**
+     * setReactive:
+     * @reactive: %true iff the workspace should be reactive
+     *
+     * Set the workspace (desktop) reactive
+     **/
+    setReactive: function(reactive) {
+        this._desktop.actor.reactive = reactive;
+    },
+
     _updateFramePosition : function() {
         this._frame.set_position(this._desktop.actor.x - FRAME_SIZE / this.actor.scale_x,
                                  this._desktop.actor.y - FRAME_SIZE / this.actor.scale_y);
@@ -1350,19 +1367,19 @@ Workspace.prototype = {
     },
 
     // Animate the full-screen to Overview transition.
-    zoomToOverview : function(animate) {
+    zoomToOverview : function() {
         this.actor.set_position(this.gridX, this.gridY);
         this.actor.set_scale(this.scale, this.scale);
 
         // Position and scale the windows.
-        if (animate)
+        if (Main.overview.animationInProgress)
             this.positionWindows(WindowPositionFlags.ANIMATE | WindowPositionFlags.ZOOM);
         else
             this.positionWindows(WindowPositionFlags.ZOOM);
 
-        let active = global.screen.get_active_workspace_index();
-        let fadeInIcons = (animate &&
-                           active == this.workspaceNum &&
+        let active = global.screen.get_active_workspace();
+        let fadeInIcons = (Main.overview.animationInProgress &&
+                           active == this.metaWorkspace &&
                            !this._haveMaximizedWindows());
         this._desktop.zoomToOverview(fadeInIcons);
 
@@ -1412,8 +1429,8 @@ Workspace.prototype = {
             }
         }
 
-        let active = global.screen.get_active_workspace_index();
-        let fadeOutIcons = (active == this.workspaceNum &&
+        let active = global.screen.get_active_workspace();
+        let fadeOutIcons = (active == this.metaWorkspace &&
                             !this._haveMaximizedWindows());
         this._desktop.zoomFromOverview(fadeOutIcons);
 
@@ -1494,8 +1511,8 @@ Workspace.prototype = {
         }
         Tweener.removeTweens(actor);
 
-        this._metaWorkspace.disconnect(this._windowAddedId);
-        this._metaWorkspace.disconnect(this._windowRemovedId);
+        this.metaWorkspace.disconnect(this._windowAddedId);
+        this.metaWorkspace.disconnect(this._windowRemovedId);
 
         if (this._repositionWindowsId > 0)
             Mainloop.source_remove(this._repositionWindowsId);
@@ -1516,7 +1533,7 @@ Workspace.prototype = {
 
     // Tests if @win belongs to this workspaces
     _isMyWindow : function (win) {
-        return win.get_workspace() == this.workspaceNum ||
+        return win.get_workspace() == this.metaWorkspace.index() ||
             (win.get_meta_window() && win.get_meta_window().is_on_all_workspaces());
     },
 
@@ -1589,14 +1606,12 @@ Workspace.prototype = {
     },
 
     _onCloneSelected : function (clone, time) {
-        Main.activateWindow(clone.metaWindow, time, this.workspaceNum);
+        Main.activateWindow(clone.metaWindow, time,
+                            this.metaWorkspace.index());
     },
 
     _removeSelf : function(actor, event) {
-        let screen = global.screen;
-        let workspace = screen.get_workspace_by_index(this.workspaceNum);
-
-        screen.remove_workspace(workspace, event.get_time());
+        screen.remove_workspace(this.metaWorkspace, event.get_time());
         return true;
     },
 
@@ -1616,12 +1631,12 @@ Workspace.prototype = {
             };
 
             let metaWindow = win.get_meta_window();
-            metaWindow.change_workspace_by_index(this.workspaceNum,
+            metaWindow.change_workspace_by_index(this.metaWorkspace.index(),
                                                  false, // don't create workspace
                                                  time);
             return true;
         } else if (source.shellWorkspaceLaunch) {
-            this._metaWorkspace.activate(time);
+            this.metaWorkspace.activate(time);
             source.shellWorkspaceLaunch();
             return true;
         }
index 519e03ec54d7c468472e0b5de969e3b46edc4ae8..dc7947c46a9611b8c147beb55a72e70f49718195 100644 (file)
@@ -38,12 +38,12 @@ const WORKSPACE_SHADOW_SCALE = (1 - WORKSPACE_DRAGGING_SCALE) / 2;
 
 const WORKSPACE_DRAG_THRESHOLD = 0.2;
 
-function GenericWorkspacesView(width, height, x, y, animate) {
-    this._init(width, height, x, y, animate);
+function GenericWorkspacesView(width, height, x, y, workspaces) {
+    this._init(width, height, x, y, workspaces);
 }
 
 GenericWorkspacesView.prototype = {
-    _init: function(width, height, x, y, animate) {
+    _init: function(width, height, x, y, workspaces) {
         this.actor = new St.Bin({ style_class: "workspaces" });
         this._actor = new Clutter.Group();
 
@@ -65,17 +65,14 @@ GenericWorkspacesView.prototype = {
         this._spacing = 0;
 
         this._windowSelectionAppId = null;
-
-        this._workspaces = [];
-
         this._highlightWindow = null;
 
         let activeWorkspaceIndex = global.screen.get_active_workspace_index();
+        this._workspaces = workspaces;
 
-        // Create and position workspace objects
-        for (let w = 0; w < global.screen.n_workspaces; w++) {
-            this._addWorkspaceActor(w);
-        }
+        // Add workspace actors
+        for (let w = 0; w < global.screen.n_workspaces; w++)
+            this._workspaces[w].actor.reparent(this._actor);
         this._workspaces[activeWorkspaceIndex].actor.raise_top();
 
         // Position/scale the desktop windows and their children after the
@@ -87,13 +84,9 @@ GenericWorkspacesView.prototype = {
                                  Lang.bind(this, function() {
                 this._onRestacked();
                 for (let w = 0; w < this._workspaces.length; w++)
-                    this._workspaces[w].zoomToOverview(animate);
+                    this._workspaces[w].zoomToOverview();
         }));
 
-        // Track changes to the number of workspaces
-        this._nWorkspacesNotifyId =
-            global.screen.connect('notify::n-workspaces',
-                                  Lang.bind(this, this._workspacesChanged));
         this._switchWorkspaceNotifyId =
             global.window_manager.connect('switch-workspace',
                                           Lang.bind(this, this._activeWorkspaceChanged));
@@ -195,12 +188,7 @@ GenericWorkspacesView.prototype = {
     },
 
     _onDestroy: function() {
-        for (let w = 0; w < this._workspaces.length; w++)
-            this._workspaces[w].destroy();
-        this._workspaces = [];
-
         Main.overview.disconnect(this._overviewShowingId);
-        global.screen.disconnect(this._nWorkspacesNotifyId);
         global.window_manager.disconnect(this._switchWorkspaceNotifyId);
         global.screen.disconnect(this._restackedNotifyId);
     },
@@ -256,32 +244,28 @@ GenericWorkspacesView.prototype = {
         throw new Error("Not implemented");
     },
 
-    _positionWorkspaces: function() {
+    updateWorkspaces: function() {
         throw new Error("Not implemented");
     },
 
-    _workspacesChanged: function() {
+    _positionWorkspaces: function() {
         throw new Error("Not implemented");
     },
 
     _activeWorkspaceChanged: function() {
         throw new Error("Not implemented");
-    },
-
-    _addWorkspaceActor: function() {
-        throw new Error("Not implemented");
     }
 };
 
-function MosaicView(width, height, x, y, animate) {
-    this._init(width, height, x, y, animate);
+function MosaicView(width, height, x, y, workspaces) {
+    this._init(width, height, x, y, workspaces);
 }
 
 MosaicView.prototype = {
     __proto__: GenericWorkspacesView.prototype,
 
-    _init: function(width, height, x, y, animate) {
-        GenericWorkspacesView.prototype._init.call(this, width, height, x, y, animate);
+    _init: function(width, height, x, y, workspaces) {
+        GenericWorkspacesView.prototype._init.call(this, width, height, x, y, workspaces);
 
         this.actor.style_class = "workspaces mosaic";
         this._actor.set_clip(x - Workspace.FRAME_SIZE,
@@ -342,30 +326,15 @@ MosaicView.prototype = {
         }
     },
 
-    _workspacesChanged: function() {
-        let oldNumWorkspaces = this._workspaces.length;
-        let newNumWorkspaces = global.screen.n_workspaces;
-
-        if (oldNumWorkspaces == newNumWorkspaces)
-            return;
-
+    updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces, lostWorkspaces) {
         let oldScale = this._workspaces[0].scale;
         let oldGridWidth = Math.ceil(Math.sqrt(oldNumWorkspaces));
         let oldGridHeight = Math.ceil(oldNumWorkspaces / oldGridWidth);
-        let lostWorkspaces = [];
 
-        if (newNumWorkspaces > oldNumWorkspaces) {
-            // Create new workspace groups
-            for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
-                this._addWorkspaceActor(w);
-            }
-
-        } else {
-            // Truncate the list of workspaces
-            // FIXME: assumes that the workspaces are being removed from
-            // the end of the list, not the start/middle
-            lostWorkspaces = this._workspaces.splice(newNumWorkspaces);
-        }
+        // Add actors
+        if (newNumWorkspaces > oldNumWorkspaces)
+            for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++)
+                this._actor.add_actor(this._workspaces[w].actor);
 
         // Figure out the new layout
         this._positionWorkspaces();
@@ -401,8 +370,6 @@ MosaicView.prototype = {
                 let workspace = lostWorkspaces[w];
                 workspace.slideOut(function () { workspace.destroy(); });
             }
-
-            // FIXME: deal with windows on the lost workspaces
         }
 
         // Reset the selection state; if we went from > 1 workspace to 1,
@@ -416,12 +383,6 @@ MosaicView.prototype = {
         this._workspaces[to].setSelected(true);
     },
 
-    _addWorkspaceActor: function(workspaceNum) {
-        let workspace  = new Workspace.Workspace(workspaceNum, this._actor);
-        this._workspaces[workspaceNum] = workspace;
-        this._actor.add_actor(workspace.actor);
-    },
-
     createControllerBar: function() {
         return null;
     },
@@ -435,8 +396,9 @@ MosaicView.prototype = {
     },
 
     removeWorkspace: function() {
-        let last = this._workspaces.length - 1;
-        global.screen.remove_workspace(this._workspaces[last]._metaWorkspace,
+        let removedIndex = this._workspaces.length - 1;
+        let metaWorkspace = this._workspaces[removedIndex].metaWorkspace;
+        global.screen.remove_workspace(metaWorkspace,
                                        global.get_current_time());
     },
 
@@ -473,14 +435,14 @@ NewWorkspaceArea.prototype = {
     }
 };
 
-function SingleView(width, height, x, y, animate) {
-    this._init(width, height, x, y, animate);
+function SingleView(width, height, x, y, workspaces) {
+    this._init(width, height, x, y, workspaces);
 }
 
 SingleView.prototype = {
     __proto__: GenericWorkspacesView.prototype,
 
-    _init: function(width, height, x, y, animate) {
+    _init: function(width, height, x, y, workspaces) {
         this._newWorkspaceArea = new NewWorkspaceArea();
         this._leftShadow = new St.Bin({ style_class: 'left-workspaces-shadow',
                                         width: Math.ceil(global.screen_width * WORKSPACE_SHADOW_SCALE),
@@ -491,7 +453,7 @@ SingleView.prototype = {
                                          height: global.screen_height,
                                          x: global.screen_width })
 
-        GenericWorkspacesView.prototype._init.call(this, width, height, x, y, animate);
+        GenericWorkspacesView.prototype._init.call(this, width, height, x, y, workspaces);
 
         this._actor.add_actor(this._newWorkspaceArea.actor);
         this._actor.add_actor(this._leftShadow);
@@ -518,6 +480,8 @@ SingleView.prototype = {
         this._buttonPressId = 0;
         this._capturedEventId = 0;
         this._timeoutId = 0;
+        this._windowDragBeginId = 0;
+        this._windowDragEndId = 0;
     },
 
     _positionWorkspaces: function() {
@@ -598,6 +562,10 @@ SingleView.prototype = {
             this._dragIndex = index;
             this._buttonPressId = dragActor.connect('button-press-event',
                                       Lang.bind(this, this._onButtonPress));
+            this._windowDragBeginId = this._workspaces[index].connect('window-drag-begin',
+                                       Lang.bind(this, this._onWindowDragBegin));
+            this._windowDragEndId = this._workspaces[index].connect('window-drag-end',
+                                       Lang.bind(this, this._onWindowDragEnd));
         } else {
             this._dragIndex = -1;
 
@@ -611,6 +579,16 @@ SingleView.prototype = {
                 global.stage.disconnect(this._capturedEventId);
                 this._capturedEventId = 0;
             }
+
+            if (this._windowDragBeginId > 0) {
+                this._workspaces[index].disconnect(this._windowDragBeginId);
+                this._windowDragBeginId = 0;
+            }
+
+            if (this._windowDragEndId > 0) {
+                this._workspaces[index].disconnect(this._windowDragEndId);
+                this._windowDragEndId = 0;
+            }
         }
     },
 
@@ -648,7 +626,7 @@ SingleView.prototype = {
                     activate++;
 
                 if (activate != active) {
-                    let workspace = this._workspaces[activate]._metaWorkspace;
+                    let workspace = this._workspaces[activate].metaWorkspace;
                     workspace.activate(global.get_current_time());
                 } else {
                     this._scrollToActive(true);
@@ -820,45 +798,21 @@ SingleView.prototype = {
         }
     },
 
-    _workspacesChanged: function() {
-        let oldNumWorkspaces = this._workspaces.length;
-        let newNumWorkspaces = global.screen.n_workspaces;
+    updateWorkspaces: function(oldNumWorkspaces, newNumWorkspaces, lostWorkspaces) {
        let active = global.screen.get_active_workspace_index();
 
-        if (oldNumWorkspaces == newNumWorkspaces)
-            return;
-
         if (this._scroll != null)
             this._scroll.adjustment.upper = newNumWorkspaces;
 
         if (newNumWorkspaces > oldNumWorkspaces) {
-            // Create new workspace groups
             for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++)
-                this._addWorkspaceActor(w);
+                this._actor.add_actor(this._workspaces[w].actor);
+
             this._positionWorkspaces();
             this._updateWorkspaceActors();
             this._scrollScrollBarToIndex(active + 1, false);
-
         } else {
-            let active = global.screen.get_active_workspace_index();
-            let removedNum = oldNumWorkspaces - newNumWorkspaces;
-            let removedIndex = active + 1;
-            this._lostWorkspaces = this._workspaces.splice(removedIndex,
-                                                           removedNum);
-
-            // Don't let the user try to select this workspace as it's
-            // making its exit.
-            for (let l = 0; l < this._lostWorkspaces.length; l++)
-                this._lostWorkspaces[l]._desktop.actor.reactive = false;
-
-            // reassign workspaceNum and metaWorkspace, as lost workspaces
-            // have not necessarily been removed from the end
-            for (let i = removedIndex; i < this._workspaces.length; i++) {
-                let metaWorkspace = global.screen.get_workspace_by_index(i);
-                this._workspaces[i].workspaceNum = i;
-                this._workspaces[i]._metaWorkspace = metaWorkspace;
-            }
-
+            this._lostWorkspaces = lostWorkspaces;
             this._scrollScrollBarToIndex(active, false);
             this._scrollToActive(true);
         }
@@ -919,7 +873,7 @@ SingleView.prototype = {
     },
 
     _onWindowDragBegin: function(w, actor) {
-        if (!this._scroll || this._scroll.adjustment.value - Math.round(this._scroll.adjustment.value) != 0)
+        if (!this._scroll || this._scrolling)
             return;
 
         this._inDrag = true;
@@ -933,14 +887,14 @@ SingleView.prototype = {
 
         let activeWorkspaceIndex = global.screen.get_active_workspace_index();
         if (x == 0 && activeWorkspaceIndex > 0 && this._dragOverLastX !== 0) {
-            this._workspaces[activeWorkspaceIndex - 1]._metaWorkspace.activate(global.get_current_time());
+            this._workspaces[activeWorkspaceIndex - 1].metaWorkspace.activate(global.get_current_time());
             this._workspaces[activeWorkspaceIndex - 1].setReservedSlot(actor._delegate);
             this._dragOverLastX = 0;
             return;
         }
         if (x == global.screen_width - 1 && this._workspaces[activeWorkspaceIndex + 1] &&
             this._dragOverLastX != global.screen_width - 1) {
-            this._workspaces[activeWorkspaceIndex + 1]._metaWorkspace.activate(global.get_current_time());
+            this._workspaces[activeWorkspaceIndex + 1].metaWorkspace.activate(global.get_current_time());
             this._workspaces[activeWorkspaceIndex + 1].setReservedSlot(actor._delegate);
             this._dragOverLastX = global.screen_width - 1;
             return;
@@ -976,7 +930,7 @@ SingleView.prototype = {
                 this._timeoutId = Mainloop.timeout_add_seconds (1, Lang.bind(this, function() {
                    let i = global.screen.get_active_workspace_index();
                    if (this._workspaces[i + onPanel]) {
-                       this._workspaces[i + onPanel]._metaWorkspace.activate(global.get_current_time());
+                       this._workspaces[i + onPanel].metaWorkspace.activate(global.get_current_time());
                        this._workspaces[i + onPanel].setReservedSlot(actor._delegate);
                    }
                    return true;
@@ -1003,14 +957,6 @@ SingleView.prototype = {
             this._workspaces[i].setReservedSlot(null);
     },
 
-    _addWorkspaceActor: function(workspaceNum) {
-        let workspace  = new Workspace.Workspace(workspaceNum, this._actor);
-        workspace.connect('window-drag-begin', Lang.bind(this, this._onWindowDragBegin));
-        workspace.connect('window-drag-end', Lang.bind(this, this._onWindowDragEnd));
-        this._actor.add_actor(workspace.actor);
-        this._workspaces[workspaceNum] = workspace;
-    },
-
     // handle changes to the scroll bar's adjustment:
     // sync the workspaces' positions to the position of the scroll bar handle
     // and change the active workspace if appropriate
@@ -1022,7 +968,7 @@ SingleView.prototype = {
         let current = Math.round(adj.value);
 
         if (active != current) {
-            let metaWorkspace = this._workspaces[current]._metaWorkspace;
+            let metaWorkspace = this._workspaces[current].metaWorkspace;
 
             if (!this._scrolling) {
                 // This here is a little tricky - we get here when StScrollBar
@@ -1083,7 +1029,7 @@ SingleView.prototype = {
             activate--;
 
         if (activate != current) {
-            let metaWorkspace = this._workspaces[activate]._metaWorkspace;
+            let metaWorkspace = this._workspaces[activate].metaWorkspace;
             metaWorkspace.activate(global.get_current_time());
         }
     },
@@ -1144,13 +1090,13 @@ SingleView.prototype = {
         }
         actor.connect('clicked', Lang.bind(this, function() {
             if (this._workspaces[i] != undefined)
-                this._workspaces[i]._metaWorkspace.activate(global.get_current_time());
+                this._workspaces[i].metaWorkspace.activate(global.get_current_time());
         }));
 
         actor._delegate = {};
         actor._delegate.acceptDrop = Lang.bind(this, function(source, actor, x, y, time) {
             if (this._workspaces[i].acceptDrop(source, actor, x, y, time)) {
-                this._workspaces[i]._metaWorkspace.activate(global.get_current_time());
+                this._workspaces[i].metaWorkspace.activate(global.get_current_time());
                 return true;
             }
             else
@@ -1263,8 +1209,9 @@ SingleView.prototype = {
     },
 
     removeWorkspace: function() {
-        let active = global.screen.get_active_workspace_index();
-        global.screen.remove_workspace(this._workspaces[active]._metaWorkspace,
+        let removedIndex = global.screen.get_active_workspace_index();
+        let metaWorkspace = this._workspaces[removedIndex].metaWorkspace;
+        global.screen.remove_workspace(metaWorkspace,
                                        global.get_current_time());
     },
 
@@ -1280,8 +1227,9 @@ function WorkspacesControls() {
 
 WorkspacesControls.prototype = {
     _init: function() {
-        this.actor = null;
+        this.actor = new St.BoxLayout({ style_class: 'workspaces-bar' });
         this._gconf = Shell.GConf.get_default();
+
         this._toggleViewButton = null;
         this._addButton = null;
         this._removeButton = null;
@@ -1292,9 +1240,11 @@ WorkspacesControls.prototype = {
         else
             this._currentViewType = WorkspacesViewType.SINGLE;
 
-        this._currentView = null;
-        this._nWorkspacesNotifyId = 0;
-        this._switchWorkspaceNotifyId = 0;
+        this._nWorkspacesNotifyId =
+            global.screen.connect('notify::n-workspaces',
+                                  Lang.bind(this, this._workspacesChanged));
+
+        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
     },
 
     _updateToggleButtonStyle: function() {
@@ -1310,28 +1260,10 @@ WorkspacesControls.prototype = {
         this._currentViewType = view;
         this._updateToggleButtonStyle();
         this._gconf.set_string(WORKSPACES_VIEW_KEY, view);
-        this.emit('view-changed');
-    },
-
-    createCurrentWorkspaceView: function(width, height, x, y, animate) {
-        switch (this._currentViewType) {
-            case WorkspacesViewType.SINGLE:
-                this._currentView = new SingleView(width, height, x, y, animate);
-                break;
-            case WorkspacesViewType.GRID:
-            default:
-                this._currentView = new MosaicView(width, height, x, y, animate);
-                break;
-        }
-        this._updateControlsBar();
-        return this._currentView;
     },
 
-    _updateControlsBar: function() {
-        if (this.actor)
-            this.actor.remove_all();
-        else
-            this.actor = new St.BoxLayout({ style_class: 'workspaces-bar' });
+    updateControls: function(currentView) {
+        this.actor.remove_all();
 
         // View switcher button
         this._toggleViewButton = new St.Button();
@@ -1347,60 +1279,53 @@ WorkspacesControls.prototype = {
         this.actor.add(this._toggleViewButton, { y_fill: false, y_align: St.Align.START });
 
         // View specific controls
-        let bar = this._currentView.createControllerBar();
-        if (!bar)
-            bar = new St.Bin();
-
-        this.actor.add(bar, { expand: true,
-                              x_fill: true,
-                              y_fill: true,
-                              y_align: St.Align.MIDDLE,
-                              x_align: St.Align.START });
+        let viewControls = currentView.createControllerBar();
+        if (!viewControls)
+            viewControls = new St.Bin();
+        this.actor.add(viewControls, { expand: true,
+                                       x_fill: true,
+                                       y_fill: true,
+                                       y_align: St.Align.MIDDLE,
+                                       x_align: St.Align.START });
 
         // Add/remove workspace buttons
         this._removeButton = new St.Button({ style_class: 'workspace-controls remove' });
         this._removeButton.connect('clicked', Lang.bind(this, function() {
-            this._currentView.removeWorkspace();
+            currentView.removeWorkspace();
         }));
         this.actor.add(this._removeButton, { y_fill: false,
                                              y_align: St.Align.START });
 
         this._addButton = new St.Button({ style_class: 'workspace-controls add' });
         this._addButton.connect('clicked', Lang.bind(this, function() {
-            this._currentView.addWorkspace()
+            currentView.addWorkspace()
         }));
         this._addButton._delegate = this._addButton;
         this._addButton._delegate.acceptDrop = Lang.bind(this,
             function(source, actor, x, y, time) {
-                let view = this._currentView;
+                let view = currentView;
                 return view._acceptNewWorkspaceDrop(source, actor, x, y, time);
+return false;
             });
         this.actor.add(this._addButton, { y_fill: false,
                                           y_align: St.Align.START });
 
-        this._nWorkspacesNotifyId =
-            global.screen.connect('notify::n-workspaces',
-                                  Lang.bind(this, this._workspacesChanged));
-
-        this._switchWorkspaceNotifyId =
-            global.window_manager.connect('switch-workspace',
-                                          Lang.bind(this, this._updateButtonSensitivity));
+        this._workspacesChanged();
+    },
 
-        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+    setCanRemove: function(canRemove) {
+        this._setButtonSensitivity(this._removeButton, canRemove);
+    },
 
-        this._workspacesChanged();
+    setCanAdd: function(canAdd) {
+        this._setButtonSensitivity(this._addButton, canAdd);
     },
 
     _onDestroy: function() {
-        this.actor = null;
         if (this._nWorkspacesNotifyId > 0) {
             global.screen.disconnect(this._nWorkspacesNotifyId);
             this._nWorkspacesNotifyId = 0;
         }
-        if (this._switchWorkspaceNotifyId > 0) {
-            global.window_manager.disconnect(this._switchWorkspaceNotifyId);
-            this._switchWorkspaceNotifyId = 0;
-        }
     },
 
     _setButtonSensitivity: function(button, sensitive) {
@@ -1410,24 +1335,145 @@ WorkspacesControls.prototype = {
         button.opacity = sensitive ? 255 : 85;
     },
 
-    _updateButtonSensitivity: function() {
-        this._setButtonSensitivity(this._addButton,
-                                   this._currentView.canAddWorkspace());
-        this._setButtonSensitivity(this._removeButton,
-                                   this._currentView.canRemoveWorkspace());
-    },
-
     _workspacesChanged: function() {
-        if (this.actor == null)
-            return;
-
-        this._updateButtonSensitivity();
-
         if (global.screen.n_workspaces == 1)
             this._toggleViewButton.hide();
         else
             this._toggleViewButton.show();
     }
 };
-
 Signals.addSignalMethods(WorkspacesControls.prototype);
+
+function WorkspacesManager(width, height, x, y) {
+    this._init(width, height, x, y);
+}
+
+WorkspacesManager.prototype = {
+    _init: function(width, height, x, y) {
+        this._workspacesWidth = width;
+        this._workspacesHeight = height;
+        this._workspacesX = x;
+        this._workspacesY = y;
+
+        this._workspaces = [];
+        for (let w = 0; w < global.screen.n_workspaces; w++) {
+            let metaWorkspace = global.screen.get_workspace_by_index(w);
+            this._workspaces[w] = new Workspace.Workspace(metaWorkspace);
+        }
+
+        this.workspacesView = null;
+        this.controlsBar = new WorkspacesControls();
+        this._updateView();
+
+        this.controlsBar.actor.connect('destroy',
+                                       Lang.bind(this, this._onDestroy));
+        this._viewChangedId =
+            Shell.GConf.get_default().connect('changed::' + WORKSPACES_VIEW_KEY,
+                                              Lang.bind(this, this._updateView));
+        this._nWorkspacesNotifyId =
+            global.screen.connect('notify::n-workspaces',
+                                  Lang.bind(this, this._workspacesChanged));
+        this._switchWorkspaceNotifyId =
+            global.window_manager.connect('switch-workspace',
+                                          Lang.bind(this, this._updateControlsSensitivity));
+
+    },
+
+    _updateView: function() {
+        let viewType, newView;
+
+        let view = Shell.GConf.get_default().get_string(WORKSPACES_VIEW_KEY).toUpperCase();
+        if (view in WorkspacesViewType)
+            viewType = WorkspacesViewType[view];
+        else
+            viewType = WorkspacesViewType.SINGLE;
+
+        switch (viewType) {
+            case WorkspacesViewType.SINGLE:
+                newView = new SingleView(this._workspacesWidth,
+                                         this._workspacesHeight,
+                                         this._workspacesX,
+                                         this._workspacesY,
+                                         this._workspaces);
+                break;
+            case WorkspacesViewType.GRID:
+            default:
+                newView = new MosaicView(this._workspacesWidth,
+                                         this._workspacesHeight,
+                                         this._workspacesX,
+                                         this._workspacesY,
+                                         this._workspaces);
+                break;
+        }
+
+        if (this.workspacesView)
+            this.workspacesView.destroy();
+        this.workspacesView = newView;
+
+        this.controlsBar.updateControls(this.workspacesView);
+
+        this.emit('view-changed');
+    },
+
+    _workspacesChanged: function() {
+        let oldNumWorkspaces = this._workspaces.length;
+        let newNumWorkspaces = global.screen.n_workspaces;
+        let active = global.screen.get_active_workspace_index();
+
+        if (oldNumWorkspaces == newNumWorkspaces)
+            return;
+
+        let lostWorkspaces = [];
+        if (newNumWorkspaces > oldNumWorkspaces) {
+            // Assume workspaces are only added at the end
+            for (let w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
+                let metaWorkspace = global.screen.get_workspace_by_index(w);
+                this._workspaces[w] = new Workspace.Workspace(metaWorkspace);
+            }
+        } else {
+            // Assume workspaces are only removed sequentially
+            // (e.g. 2,3,4 - not 2,4,7)
+            let removedIndex;
+            let removedNum = oldNumWorkspaces - newNumWorkspaces;
+            for (let w = 0; w < oldNumWorkspaces; w++) {
+                let metaWorkspace = global.screen.get_workspace_by_index(w);
+                if (this._workspaces[w].metaWorkspace != metaWorkspace) {
+                    removedIndex = w;
+                    break;
+                }
+            }
+
+            lostWorkspaces = this._workspaces.splice(removedIndex,
+                                                     removedNum);
+
+            // Don't let the user try to select this workspace as it's
+            // making its exit.
+            for (let l = 0; l < lostWorkspaces.length; l++)
+                lostWorkspaces[l].setReactive(false);
+        }
+
+        this.workspacesView.updateWorkspaces(oldNumWorkspaces,
+                                             newNumWorkspaces,
+                                             lostWorkspaces);
+
+        this.controlsBar.setCanAdd(this.workspacesView.canAddWorkspace());
+        this.controlsBar.setCanRemove(this.workspacesView.canRemoveWorkspace());
+    },
+
+    _updateControlsSensitivity: function() {
+        this.controlsBar.setCanAdd(this.workspacesView.canAddWorkspace());
+        this.controlsBar.setCanRemove(this.workspacesView.canRemoveWorkspace());
+    },
+
+    _onDestroy: function() {
+        if (this._nWorkspacesNotifyId > 0)
+            global.screen.disconnect(this._nWorkspacesNotifyId);
+        if (this._switchWorkspaceNotifyId > 0)
+            global.window_manager.disconnect(this._switchWorkspaceNotifyId);
+        if (this._viewChangedId > 0)
+            Shell.GConf.get_default().disconnect(this._viewChangedId);
+        for (let w = 0; w < this._workspaces.length; w++)
+            this._workspaces[w].destroy();
+    }
+};
+Signals.addSignalMethods(WorkspacesManager.prototype);